﻿using System;
using System.Threading;
using Microsoft.SPOT;
///The MFWebSocket Community Example
namespace MFWebSocket
{
    using MFWebSocket.Contract;
    using MFWebSocket.Contract.Clients;
    public class Program
    {
        /// <summary>
        /// Main Entry Point of the Application
        /// </summary>
        public static void Main()
        {
            //Run the unit tests for this application
            UnitTests();

            //Instantiate a WebSocketClient
            using (WebSocketClient webSocket = new WebSocketClient("ws://ws.pusherapp.com:80/app/de39425369c0ebdccb7e?client=js&version=1.8.5"))
            {                
                try
                {
                    //Attach instance handlers
                    webSocket.Open += new OpenHandler(wsc_Open);
                    webSocket.Close += new CloseHandler(wsc_Close);
                    webSocket.Message += new MessageHandler(wsc_MsgRecieved);

                    //Keep track of how long it takes to connect
                    DateTime start = DateTime.Now, timer = start;
                  
                    //Connect
                    webSocket.Connect();
                    
                    //Let other threads have more processor time
                    Thread.CurrentThread.Priority = ThreadPriority.Lowest;

                    Debug.Print("Wait for connection and handshake to occur, if we were previously connected then this may not happen but we are still connected");
                    while (!webSocket.Connected) Debug.Print("Connecting.....");

                    //Calculate how long it took to connect
                    TimeSpan initialConnectionTime = DateTime.Now - start, totalConnectionTime = new TimeSpan();
                    
                    //Show how it took to connect in the Output window
                    Debug.Print("Connected! Connection TimeSpan: " + initialConnectionTime.ToString() + "\r\n");
                    
                    //In this example we are sending a command relevant to the Pusher endpoint described in the WebSocketClient constructor
                    string message = "{'event':'pusher:subscribe', 'data':{'channel':'test_channel'}}";

                    //Send Subscribe command
                    webSocket.Send(message);
                    
                    //If we sent the message then print what we sent in the Output window.
                    if (webSocket.BytesSent > 0) Debug.Print("Sent: " + message);
                    //Else throw an execption to stop execution and dispose the WebSocketClient
                    else throw new Exception("We didn't send a message");
                    Debug.Print("Print to the output while we have not recived anything from the host:");
                    while (webSocket.BytesRecieved == 0)
                    {                        
                        Debug.Print("\tNo message recieved yet sleeping for connectionTime: " + initialConnectionTime.ToString() + "\r\n Right Now=> " + (timer = DateTime.Now) + "\r\nNext Sleep should occur at=> " + (start = timer.Add(initialConnectionTime)) + "\r\n\r\n");
                        Thread.Sleep(initialConnectionTime.Milliseconds);
                        totalConnectionTime += DateTime.Now - start;
                        if (start.Ticks - timer.Ticks - initialConnectionTime.Ticks != 0 || totalConnectionTime.Minutes > 3)
                        {
                            //Time to stop execution
                            Exception ex = new Exception("User Created->More ticks elapsed in a sleep then in the connection time or three minutes since we have recieved a response from the server");
                            //Log to the output
                            Tools.LogException("Program:Main", ex);
                            //Throw the exception
                            throw ex;
                        }
                        else Debug.Print("Connected for: " + totalConnectionTime);
                    }                    
                    Debug.Print("Recieved a message!\r\n Recive other messages with handler, close emulator or power off device to stop exeuction");

                    //Sleep forever while recieving messages
                    Thread.Sleep(Timeout.Infinite);
                }
                catch (Exception ex)
                {
                    //Something bad happened
                    Debug.Print(ex.Message);
                    Debug.Print("\r\n");
                    Debug.Print(ex.StackTrace);
                }
                finally
                {
                    //This is all for this example..
                    //However if we were not in a using scope we should Dispose the WebSocketClient if we no longer need it
                    //Good byte wsc!
                    //wsc.Dispose();
                    //wsc = null;
                    //Or close the socket to save resources and processing time

                    //If we are connected still
                    if (webSocket.Connected)
                    {
                        //We want to unsubscribe
                        webSocket.Send("{'event':'pusher:unsubscribe', 'data':{'channel':'test_channel'}}");

                        //Called so we can observe the Close Event before removing the handler
                        webSocket.CloseSocket();
                    }

                    //Remove event handlers
                    webSocket.Open -= wsc_Open;
                    webSocket.Close -= wsc_Close;
                    webSocket.Message -= wsc_MsgRecieved;
                }
            }
        }

        /// <summary>
        /// Performs some unit test
        /// </summary>
        private static void UnitTests()
        {
            try
            {
                ///Perform UnitTests
                TestCount();
                TestReverse();
                TestMD5();
                TestUriEncode();
                TestIndexOfBytes();
                TestCombineArrays();
            }
            catch(Exception ex)
            {
                Tools.LogException("Program:UnitTests=>", ex);
                throw;
            }
        }

        #region Event Handlers

        /// <summary>
        /// Handle the close event
        /// </summary>
        /// <param name="sender"></param>
        internal static void wsc_Close(IWebSocketClient sender)
        {
            Debug.Print("WebSocketClient + Closed");
        }

        /// <summary>
        /// Handle the open event
        /// </summary>
        /// <param name="sender"></param>
        internal static void wsc_Open(IWebSocketClient sender)
        {
            Debug.Print("WebSocketClient + Opened");
        }

        /// <summary>
        /// Handle the MessageEvent
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        internal static void wsc_MsgRecieved(IWebSocketClient sender, MessageArgs args)
        {
            //Print the message recieved in the window
            {
                if (args != null) Debug.Print(String.Concat("Message=", args.Message));                
            }
        }

        #endregion

        #region Unit Tests

        public static void TestIndexOfBytes()
        {
            //Simulate finding the expected response in the buffer

            //Generate Security Keys
            string secKey1 = new String(System.Text.Encoding.UTF8.GetChars(WebSocketClient.GenerateSecKey()));

            string secKey2 = new String(System.Text.Encoding.UTF8.GetChars(WebSocketClient.GenerateSecKey()));

            byte[] secKey3 = WebSocketClient.GenerateSecKey(8);

            //Generate Expected Response
            byte[] expectedResponse = WebSocketClient.GetResponseSecurityKey(secKey1, secKey2, secKey3);

            //Build Handshake
            System.Text.StringBuilder handshakeBuilder = new System.Text.StringBuilder();

            handshakeBuilder.AppendLine(string.Concat("GET ", "/app/de39425369c0ebdccb7e?client=js&version=1.8.5 HTTP/1.1\r\n", " HTTP/1.1"));
            handshakeBuilder.AppendLine("Upgrade: WebSocket");
            handshakeBuilder.AppendLine("Connection: Upgrade");
            handshakeBuilder.AppendLine(string.Concat("Sec-WebSocket-Key2: ", secKey2));
            handshakeBuilder.AppendLine(string.Concat("Host: ", "ws.pusherapp.com"));
            handshakeBuilder.AppendLine(string.Concat("Sec-WebSocket-Key1: ", secKey1));
            handshakeBuilder.AppendLine(string.Concat("Origin:", "ws.pusherapp.com"));

            //Include Cookies and Security Key
         
            handshakeBuilder.AppendLine();
            //empty chars to we can insert at a random place
            handshakeBuilder.Append(new char[expectedResponse.Length]);
            byte[] search = System.Text.Encoding.UTF8.GetBytes(handshakeBuilder.ToString());
            //randomly choose at
            int ShouldBeAt = Tools.Random(0, search.Length - expectedResponse.Length), mover = (ShouldBeAt % 2 == 0 ? 0 : 1);
            expectedResponse.CopyTo(search, ShouldBeAt);

            //Where did we find it
            int foundIndex = Tools.IndexOfBytes(expectedResponse, 0, expectedResponse.Length, search);
            //Spot match
            int foundIndex2 = Tools.IndexOfBytes(expectedResponse, 0, expectedResponse.Length, search, 2);
            //Match reverse
            int foundReverse = Tools.IndexOfBytes(expectedResponse, search.Length - 1, expectedResponse.Length, search, -1);
            //Spot match reverse
            int foundReverse2 = Tools.IndexOfBytes(expectedResponse, search.Length - 1, expectedResponse.Length, search, -2);
            //Make sure they all match eachother and shouldbeat
            if (foundIndex != foundIndex2 || foundReverse != foundIndex || foundReverse2 != foundIndex2 && foundIndex != ShouldBeAt) throw new Exception("TestFailed");
        }

        internal static void TestUriEncode()
        {
            string toEscape = @"http://www.someDomain.com/path/?var=2&3=f";
            string escaped = Tools.EscapeUriString(toEscape);
            bool result =  toEscape != escaped;
        }

        internal static void TestMD5()
        {            
            byte[] hash = MD5.GetHash("Test", System.Text.UTF8Encoding.UTF8);
            bool result = !(hash.Length == 0);
        }

        internal static void TestReverse()
        {
            byte[] test = {0x00, 0x01, 0x02, 0x03, 0x04};
            Tools.Reverse(test);
            bool success = test[0] == 0x04;
        }

        internal static void TestCount()
        {
            string test = "TESTT!";
            char fFor = 'T';
            int count = Tools.CountChars(ref test, ref fFor);
            bool success = count == 3;
        }

        internal static void TestCombineArrays()
        {
            if (Tools.CombineArrays(null) != null) throw new Exception("test failed");
            byte[] result = Tools.CombineArrays(new byte[] { 0x00, 0x00 }, new byte[] { 0x01, 0x01 });
            if (result.Length != 4 || result[0] != 0x00 && result[1] != 0x00 && result[2] != 0x01 && result[3] != 0x01) throw new Exception("test fails");
            return;
        }

        #endregion

    }
}
